﻿/********************************************************************************
* Copyright 2010 Zane Thorn (zane.thorn@gmail.com)                              *
*                                                                               *
* NeturalMath is free software: you can redistribute it and/or modify           *
* it under the terms of the GNU Lesser General Public License as published by   *
* the Free Software Foundation, either version 3 of the License, or             *
* (at your option) any later version.                                           *
*                                                                               *
* NeturalMath is distributed in the hope that it will be useful,                *
* but WITHOUT ANY WARRANTY; without even the implied warranty of                *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                 *
* GNU Lesser General Public License for more details.                           *
*                                                                               *
* You should have received a copy of the GNU Lesser General Public License      *
* along with NeturalMath.  If not, see <http://www.gnu.org/licenses/>.          *
********************************************************************************/

using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using NeturalMath.Expressions;
using NeturalMath.Properties;

namespace NeturalMath
{
    /// <summary>
    /// The Domain object represents a NeturalMath domain, and provides support for many lookup and scoping functions.  Domains are the fundamental unit
    /// of organization in NeturalMath, similar to classes in other systems.
    /// </summary>
    public class Domain:BlockSymbol
    {
       
        #region Constructors

        /// <summary>
        /// Creates a new instance of a domain.  To only be used internally.
        /// </summary>
        /// <param name="name"></param>
        /// <param name="parentDomain"></param>
        protected internal Domain(string name,Domain parentDomain,MathRuntime runtime,DomainExpression expression,Scope scope)
            :base(name,parentDomain,runtime,expression,scope)
        {
            
        }

        #endregion

        #region Public Methods

        #region Symbol Methods

        public override MathSymbol CloneSymbol(string symbolName, MathSymbol sourceSymbol, Scope scope = default(Scope))
        {
            // trying to assign to a value which does not exist!
            if (HasSymbol(symbolName))
                throw new MathException(ErrorCodes.DuplicateVariable, string.Format(Resources.MemberNameAlreadyExists_M, symbolName));

            // Check to see if the symbol is a domain, if so, clone the domain
            var lookupDomain = sourceSymbol as Domain;
            if (lookupDomain != null)
                return CloneDomain(symbolName, lookupDomain, scope);

            return base.CloneSymbol(symbolName, sourceSymbol, scope);
        }

        #endregion

        #region Domain Methods

        public IEnumerable<Domain> LocalDomains
        {
            get { return LocalSymbols.Where(d => d is Domain).Select(d=>(Domain)d);}
        }
        
        public IEnumerable<Domain> AllDomains
        {
            get { return AllSymbols.Where(d => d is Domain).Select(d=>(Domain)d);}
        }

        public Domain CreateDomain(string symbolName, DomainExpression expression, Scope scope = default(Scope))
        {
            if (HasSymbol(symbolName))
                throw new MathException(ErrorCodes.DuplicateVariable,string.Format(Resources.MemberNameAlreadyExists_M,symbolName));

            var domain = new Domain(symbolName, this,Runtime, expression, scope);

            if (scope.Accessability == AccessabilityLevels.Global && !this.Equals(Runtime.RootDomain))
                Runtime.RootDomain.Symbols.Add(symbolName, domain);
                
            Symbols.Add(symbolName, domain);

            return domain;
        }

        public Domain CloneDomain(string newDomainName, Domain sourceDomain,Scope scope = default(Scope))
        {
            if (HasSymbol(newDomainName))
                throw new MathException(ErrorCodes.DuplicateVariable, string.Format(Resources.MemberNameAlreadyExists_M, newDomainName));

            // cannot recursively define a domain
            if (sourceDomain == this)
                throw new MathException(ErrorCodes.CannotCloneSelf,Resources.CannotCloneSelf);

            var parent = ParentScope;
            while (parent!=null)
            {
                if (parent==sourceDomain)
                    throw new MathException(ErrorCodes.CannotCloneParent,Resources.CannotCloneParent);
                parent = parent.ParentScope;
            }

            var visitor = new ExpressionCloningVisitor(Runtime);
            var expression = (DomainExpression)visitor.Visit(sourceDomain.Value);

            // Create the domain based on the new domain parameters
            var domain = CreateDomain(
                newDomainName, 
                expression, 
                scope
                );

            domain.Invoke();

            return domain;
        }


        #endregion

        #endregion

        /// <summary>
        /// Returns the string representation of a domain
        /// </summary>
        /// <returns>The Name property of the domain</returns>
        public override string ToString()
        {
            return Name;
        }

        #region Dynamic Members

        /// <summary>
        /// Overrides the dynamic TryInvokeMember method.  Used to dynamically look up functions from .NET code
        /// </summary>
        /// <param name="binder"></param>
        /// <param name="args"></param>
        /// <param name="result"></param>
        /// <returns></returns>
        public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
        {
            var memberName = binder.Name;

            var currentSymbol = GetSymbol(memberName);
            if (currentSymbol == null)
                throw new MathException(ErrorCodes.MemberNotFound, string.Format(Resources.MemberNotFound_M, memberName));

            // Check to make sure we can invoke the symbol
            var callable = currentSymbol as Function;
            if (callable == null)
                throw new MathException(ErrorCodes.ExpectedFunctionOrDomain,
                                        string.Format(Resources.ExpectedFunctionOrDomain, memberName,
                                                      currentSymbol.GetType().Name));

            var parameters = args.Select(a => (MathValue)a);
            result= callable.Invoke(parameters);

            return true;
        }

        /// <summary>
        /// Overrides the dynamic TryGetMember method.  Used to dynamically look up variables from .NET code
        /// </summary>
        /// <param name="binder"></param>
        /// <param name="result"></param>
        /// <returns></returns>
        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            var variable = GetSymbol(binder.Name);
            result = variable.Value;

            return true;
        }

        /// <summary>
        /// Overrides the dynamic TrySetMember method.  Used to dynamically look up variables from .NET code.
        /// </summary>
        /// <param name="binder"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public override bool TrySetMember(SetMemberBinder binder, object value)
        {
            SetVariable(binder.Name, (ValueExpression) value);
            return true;
        }

        #endregion

    }
}
